package bsh.dyn;

import java.util.concurrent.atomic.AtomicLong;

import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;

import bsh.CompilationState;
import bsh.CompilationUnitImpl;
import bsh.CompiledScript;
import bsh.compilation.DefaultCompilationOptions;
import bsh.exception.CompilationFailedException;
import bsh.meta.JavaClassWrapper;
import bsh.utils.reflection.BytecodeHelper;
import bsh.utils.reflection.OpenClassLoader;

/**
 * Plain implementation of a {@link JITCompiler}. Generates a class
 * 
 * @author takeshi
 */
public class JITCompilerImpl implements JITCompiler {

    private final OpenClassLoader classLoader;

    private static AtomicLong seed = new AtomicLong(System.nanoTime());

    /**
     * Ctor
     * 
     * @param classLoader {@link OpenClassLoader}
     */
    public JITCompilerImpl(OpenClassLoader classLoader) {
        super();
        this.classLoader = classLoader;
    }

    /**
     * {@inheritDoc}
     */
    public Invocable compileInvocable(Invocable invocable) {
        if (!invocable.isCompilable()) {
            return invocable;
        }
        String className = "generated.p" + seed.getAndIncrement() + ".method";
        ClassWriter writer = BytecodeHelper.createClassWithEmptyConstructor(className, Object.class.getConstructors()[0], Invocable.class);
        try {
            MethodVisitor visitor =
                    BytecodeHelper.overwritePublicMethod(writer,
                            Invocable.class.getMethod("invoke", new Class[]{ CompiledScript.class, Object.class, Object[].class }));
            visitor.visitCode();
            final CompilationUnitImpl unit =
                    new CompilationUnitImpl(null, className, JavaClassWrapper.OBJECT_WRAPPER, null, new DefaultCompilationOptions());
            invocable.generateBytecode(visitor, unit, new CompilationState(unit));
            visitor.visitMaxs(0, 0);
            visitor.visitEnd();
            byte[] byteArray = writer.toByteArray();
            Class<?> defined = classLoader.defineClass(className, byteArray);
            return (Invocable) defined.newInstance();
        } catch (NoSuchMethodException e) {
            throw new IllegalStateException(e);
        } catch (CompilationFailedException e) {
            return invocable;
        } catch (InstantiationException e) {
            throw new IllegalStateException(e);
        } catch (IllegalAccessException e) {
            return invocable;
        }
    }

}
